package com.itesm.distribuidos.tarea4;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.StringTokenizer;

public class MatrixServerConexion extends Thread{
	private ServerSocket socket;
	
	public MatrixServerConexion()
	{
		try {
			socket = new ServerSocket(62000);
		} catch (IOException e) { System.out.println("IO Exception creating the socket"); }
	}
	
	public void run()
	{
		System.out.println("MatrixServerConexion started...");
		while(true) //Iniciamos el ciclo
		{
			try {
				Socket connection = socket.accept();
				ConnectionThread connectionThread = new ConnectionThread(connection);
				connectionThread.start();
				connectionThread.join();
			}
			catch (IOException e) { } 
			catch (InterruptedException e) { } 		
		}
	}
	
	public static void main(String...arg)
	{
		new MatrixServerConexion().start();
	}
}

class ConnectionThread extends Thread
{
	private MatrixOperations matrixOperations;
	private Socket connection;
	private int size, operation, hilo;
	private long ioTime;
	private long processingTime;
	private String response;
	private long initTime;
	private String[][] matrix1, matrix2;
	private String[][] a, b;
	
	ConnectionThread(Socket conn)
	{
		this.connection = conn;
		
		ObjectInputStream ois;
		try {
			ois = new ObjectInputStream(connection.getInputStream());
			Integer[] requestData = (Integer[]) ois.readObject();
			hilo = requestData[2];
			size = requestData[1];
			operation = requestData[0];
			initTime = 0l;
		} 
		catch (IOException e) { } 
		catch (ClassNotFoundException e) { }
		
		matrixOperations = new MatrixOperations();

		ioTime = 0l;
		processingTime = 0l;
	}
	
	public void run()
	{
		try {
			//Preparamos stream para mandar una respuesta
			ObjectOutputStream out = new ObjectOutputStream(connection.getOutputStream());
			out.flush();
			
			//Generamos las matrices aleatoriamente
			initTime = System.currentTimeMillis();
			a = matrixOperations.createRandomStringMatrix(size);
			b = matrixOperations.createRandomStringMatrix(size);
			processingTime = System.currentTimeMillis() - initTime;
			
			System.out.println("Starting a thread for a " + size + "x" + size + " matrix  Thread#"+hilo);
			
			//Escribimos las matrices generadas en un archivo de texto
			initTime = System.currentTimeMillis();
			createInputFiles();
			ioTime = System.currentTimeMillis() - initTime;

			//Leemos los archivos de entrada para hacer las operaciones
			initTime = System.currentTimeMillis();
			matrix1 = loadMatrixFromFile("matrix1_" + size + ".txt", size);
			matrix2 = loadMatrixFromFile("matrix2_" + size + ".txt", size);
			ioTime += System.currentTimeMillis() - initTime;
			
			//Declaramos una nueva matriz para guardar los resultados de las operaciones		
			String[][] matrix = null;
			initTime = 0l;
			
			//Liberamos memoria e imprimos informacion que recibio el server
			response = operation + "%" + size + "%";
			
			//Realizamos la operacion y medimos el tiempo
			switch(operation)
			{
			case 1:
				initTime = System.currentTimeMillis();
				matrix = matrixOperations.addition(matrix1, matrix2);
				processingTime += System.currentTimeMillis()-initTime;
				response += "Suma[" + size + "]: " + processingTime + " ms - Hilo#" + hilo;
				break;
			case 2:
				initTime = System.currentTimeMillis();
				matrix = matrixOperations.substraction(matrix1, matrix2);
				processingTime += System.currentTimeMillis()-initTime;
				response += "Resta[" + size + "]: " + processingTime + " ms - Hilo#" + hilo;
				break;
			case 3:
				initTime = System.currentTimeMillis();
				matrix = matrixOperations.multiplication(matrix1, matrix2);
				processingTime += System.currentTimeMillis()-initTime;
				response += "Multiplicacion[" + size + "]: " + processingTime + " ms - Hilo#" + hilo;
				break;
			case 4:
				initTime = System.currentTimeMillis();
				matrix = matrixOperations.division(matrix1, matrix2);
				processingTime += System.currentTimeMillis()-initTime;
				response += "Division[" + size + "]: " + processingTime + " ms - Hilo#" + hilo;
				break;
			}
			
			//Creamos el archivo con el resultado
			initTime = System.currentTimeMillis();
			this.createOutputFile(matrix, Integer.toString(size));
			ioTime += System.currentTimeMillis() - initTime;
			
			//Leemos la respuesta del archivo de resultado
			initTime = System.currentTimeMillis();
			String matrixResult = this.loadMatrixFromFileToSendResult("result_" + size + ".txt", size);
			ioTime += System.currentTimeMillis() - initTime;
			
			//Imprimimos totales de tiempo de E/S y Procesamiento
			response += "%" + ioTime + "%";
			response += processingTime + "%";
			response += matrixResult;
			
			out.writeObject(response);
			
			//Cerramos todos los streams y conexion
			out.close();
			connection.close();
		} catch (IOException e) { 
			System.out.println("FileNotFoundException IO:" + e.getMessage() + "at run()");
		}
	}
	
	private synchronized void createInputFiles(){
		try {
			File m1 = new File("matrix1_" + size + ".txt");
			File m2 = new File("matrix2_" + size + ".txt"); 
			
			if(!m1.exists())
				m1.createNewFile();
			
			if(!m2.exists())
				m2.createNewFile();
			
			FileOutputStream fos = new FileOutputStream("matrix1_" + size + ".txt"); 
			OutputStreamWriter out1 = new OutputStreamWriter(fos, "UTF-8");
			FileOutputStream fos2 = new FileOutputStream("matrix2_" + size + ".txt"); 
			OutputStreamWriter out2 = new OutputStreamWriter(fos2, "UTF-8");

			for(int i=0; i<a.length; i++)
			{
				for(int j=0; j<a.length; j++)
				{
					if(j>0){
						out1.write(",");
						out2.write(",");
					}
					out1.write(a[i][j]);
					out2.write(b[i][j]);
				}
				out1.write("\n");
				out2.write("\n");
			}
			out1.close();
			fos.close();
			out2.close();
			fos2.close();
			System.gc();
		}
		catch (UnsupportedEncodingException e) {
			System.out.println("UnsupportedEncodingException at createInputFiles");
		}
		catch (FileNotFoundException e) { 
			System.out.println("FileNotFoundException at createInputFiles: " + e.getMessage());
		}
		catch (IOException e) {
			System.out.println("FileNotFoundException IO:" + e.getMessage() + "at createInputFiles");
		}
	}
	
	public synchronized void createOutputFile(String[][] a, String suffix)
	{
		try {
			File m1 = new File("result_"+ suffix +".txt");

			if(!m1.exists())
				m1.createNewFile();
			
			FileOutputStream fos = new FileOutputStream("result_"+ suffix +".txt"); 
			OutputStreamWriter out = new OutputStreamWriter(fos, "UTF-8");
			for(int i=0; i<a.length; i++)
			{
				for(int j=0; j<a.length; j++)
				{
					if(j>0)
						out.write(",");
					out.write(a[i][j]);
				}
				out.write("\n");
			}
			out.close();
			fos.close();
			System.gc();
		}
		catch (UnsupportedEncodingException e) {
			System.out.println("UnsupportedEncodingException at createOutputFile");
		}
		catch (FileNotFoundException e) { 
			System.out.println("FileNotFoundException at createOutputFile");
		}
		catch (IOException e) {
			System.out.println("FileNotFoundException IO:" + e.getMessage() + "at createOutputFile");
		}
	}
	
	public String[][] loadMatrixFromFile(String matrixFile, int size){
		String[][] stringMatrix = new String[size][size];
		try
		{
			FileInputStream fstream = new FileInputStream(matrixFile);
			DataInputStream in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			
			String strLine;
			StringTokenizer tokenizer;

			int i = 0;
			while ((strLine = br.readLine()) != null)
			{
				tokenizer = new StringTokenizer(strLine, ",");
				if(tokenizer.countTokens() == size)
				{
					for (int j=0; j<size; j++)
						stringMatrix[i][j] = tokenizer.nextToken();
				}
				i++;
			}

			br.close();
			in.close();
			fstream.close();
		}
		catch(IOException ioe) { 
			System.out.println("FileNotFoundException IO:" + ioe.getMessage() + "at loadMatrixFromFile");
		}
		return stringMatrix;
	}
	
	public String loadMatrixFromFileToSendResult(String matrixFile, int size){
		StringBuffer stringMatrix = new StringBuffer();
		try
		{
			FileInputStream fstream = new FileInputStream(matrixFile);
			
			DataInputStream in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			
			String strLine;
			stringMatrix.append("[");
			while ((strLine = br.readLine()) != null)
			{
				stringMatrix.append("["+ strLine + "]");
			}
			stringMatrix.append("]");
			
			br.close();
			in.close();
		}
		catch(IOException ioe) { }
		return stringMatrix.toString();
	}
}